import Dep from './dep.js';

function parsePath(path) {
  const keys = path.split('.'); // ['a', 'b', 'c']
  return function (obj) {
    // obj => vm
    for (const key of keys) {
      // key => a
      // 触发a的getter => dep.depend()
      // obj[key] => { b: { c: 1 } }

      // key = b
      // 触发b的getter => dep.depend()
      // obj[key] => { c: 1 }

      // key = c
      // 触发c的getter => dep.depend()
      // obj[key] => 1
      obj = obj[key];
    }
    // obj => 1
    // 最终的值
    return obj;
  };
}

// new Watcher(vm, 'message', () => {  }) => constructor => get() => 访问数据

export default class Watcher {
  vm;
  exp;
  cb;
  value;
  getter;
  // 懒惰的，如果为true，表示不会立即触发计算属性函数，当使用到该计算属性时，才会触发
  lazy = false;
  // 肮脏的，如果为true，表示依赖的数据发生了变化，需要重新计算，否则不会重新计算
  dirty = false;

  constructor(vm, exp, cb, options = {}) {
    this.vm = vm;
    this.exp = exp;
    this.cb = cb;

    // 支持计算属性
    if (typeof exp === 'function') {
      this.getter = exp;
    } else {
      this.getter = parsePath(this.exp); // 'a.b.c'
    }

    this.lazy = !!options.lazy;

    // 如果是懒惰的，那么刚开始就应该是肮胀的
    this.dirty = this.lazy;

    // 如果不是懒惰的，才需要立即获取值
    if (!this.lazy) {
      // 要存储当前值
      this.value = this.get();
    }
  }

  get() {
    // 在触发数据的 getter之前，先将当前watcher实例存到一个公共的地方（能被Dep访问到的地方）
    Dep.target = this;

    let value = this.getter.call(this.vm, this.vm._data);

    // const _data = this.vm._data;
    // if (typeof this.exp === 'function') {
    //   value = this.exp.call(this.vm);
    // } else {
    //   // _data[this.exp] 会触发什么？=> getter => dep.depend() => 将当前wacher实例收集起来
    //   value = _data[this.exp]; // _data['a.b.c']
    // }

    // 一旦收集完，就可以清空啦
    Dep.target = null;

    return value;
  }

  // 给Dep的notify调
  update() {
    // 执行到这里，说明依赖的数据发生了变化

    // 如果是懒惰的，说明是计算属性的watcher，
    // 就先标记为肮胀的，等它使用的时候，再去计算
    if (this.lazy) {
      this.dirty = true;
    }
    // 如果不是懒惰的，说明是普通的watcher
    else {
      this.run();
    }
  }

  run() {
    // 这个是旧值
    const oldValue = this.value;
    // 这个是新值，获取并覆盖
    this.value = this.get();

    // 当新值和旧值不一样时，才需要触发回调
    if (this.value !== oldValue) {
      // if (newValue === oldValue) return
      this.cb && this.cb.call(this.vm, this.value, oldValue);
    }
  }
}
